﻿using System;
using System.Collections.Generic;

namespace SStateMachine
{
    // refer to @Stateless(https://github.com/dotnet-state-machine/stateless)
    public partial class StateMachine<TState, TTrigger>
    {
        private readonly Dictionary<TState, StateEvents> states;
        private readonly Dictionary<TTrigger, List<Transition>> triggers;
        private readonly Func<TState> stateAccessor;
        private readonly Action<TState> stateMutator;

        public TState State
        {
            get
            {
                return stateAccessor();
            }
            private set
            {
                stateMutator(value);
            }
        }

        #region Constructor
        // Instantiate collection object
        private StateMachine()
        {
            states = new Dictionary<TState, StateEvents>();
            triggers = new Dictionary<TTrigger, List<Transition>>();
        }

        // Instantiate state machine from the original state
        public StateMachine(TState initialState) : this()
        {
            StateReference stateReference = new StateReference
            {
                State = initialState
            };

            stateMutator = value => stateReference.State = value;
            stateAccessor = () => stateReference.State;
        }

        // Instatiate state machine by action
        public StateMachine(Func<TState> stateAccessor, Action<TState> stateMutator) : this()
        {
            this.stateAccessor = stateAccessor;
            this.stateMutator = stateMutator;
        }
        #endregion

        public StateConfigure Config(TState state)
        {
            if (!states.ContainsKey(state))
            {
                states.Add(state, null);
            }

            if (states[state] == null)
            {
                states[state] = new StateEvents(state);
            }

            return new StateConfigure(state, this, states[state]);
        }

        public void Trigger(TTrigger trigger)
        {
            if (!triggers.ContainsKey(trigger))
                throw new ArgumentException(StateException.TriggerNotFound);

            Transition transition = FindTransition(State, trigger);

            if (transition == null)
                throw new Exception(StateException.TriggerTransitionNotFound);

            State = transition.Destination;

            if (states[transition.Source] != null)
            {
                states[transition.Source].Exit(trigger);
            }

            if (states[transition.Destination] != null)
            {
                states[transition.Destination].Enter();
            }
        }

        private Transition FindTransition(TState source, TTrigger trigger)
        {
            Transition transition = null;
            foreach(var tr in triggers[trigger])
            {
                if (tr.Source.Equals(source))
                {
                    transition = tr;
                    break;
                }
            }
            return transition;
        }

        private void AddTrigger(TState source, TTrigger trigger, Transition transition)
        {
            if (!triggers.ContainsKey(trigger))
            {
                triggers.Add(trigger, new List<Transition>());
            }

            Transition preTransition = FindTransition(source, trigger);
            if (preTransition == null)
                triggers[trigger].Add(transition);
            else
            {
                for (int i = 0; i < triggers[trigger].Count; i++)
                {
                    if (preTransition.Equals(triggers[trigger][i]))
                    {
                        triggers[trigger][i] = transition;
                        break;
                    }
                }
                
            }
        }
    }
}
